/** 
 * CanvasPane.java
 * @author Lex
 * 2007-8-20 ����10:53:45
 */
package fun.ui.canvas;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.JPanel;

import org.lex.app.prefs.ResourceMap;
import org.lex.utils.collections.DoubleList;

import fun.configuration.CK;
import fun.configuration.Config;
import fun.symtable.UserSymbolTable;

/**
 * ��׼�Ļ�ͼ��� ÿ���������Զ�Ӧ���CanvasModel ��ʾһ�Ż����ϻ��ƶ������
 */
public class CanvasPane extends JPanel implements CanvasModelListener {
    /*
     * �����ı߿���2������ɣ�һ����getBorder()�ı߿�
     * ����һ���ǻ���������ʾ����ᡢ���ֵȵĿհ�����
     */
    private Insets borderInsets = new Insets(0, 0, 0, 0);
    // this.getWidth() - borderInsets.left - borderInsets.right;
    private int totalWidth = 0;
    // this.getHeight() - borderInsets.top - borderInsets.bottom;
    private int totalHeight = 0;
    // ===========================
    // totalWidth - left - right
    private int canvasWidth = 0;
    // totalHeight - top - bottom
    private int canvasHeight = 0;
    // ������ʾ���ֵȿհ������С
    private Insets canvasInsets = new Insets(0, 0, 0, 0);
    // =====================================================
    private Stroke stroke = new BasicStroke(1);
    private Color coordinate = Color.magenta;
    // ===============================================
    private BufferedImage img = null;
    private Graphics2D g2 = null;
    // ===============================
    private double minX = -10D;
    private double maxX = 10D;
    private DoubleList y = null;
    private YAxisPolicy yAxisPolicy = YAxisPolicy.BIND_TO_X;
    private List<CanvasModel> models = null;
    private List<Path2D.Double> curves = null;

    public CanvasPane() {
	super();
	y = new DoubleList();
	models = new ArrayList<CanvasModel>(2);
	curves = new ArrayList<Path2D.Double>(2);
	// load configurations
	ResourceMap cfg = Config.instance();
	setForeground(new Color(cfg.getInt(CK.UI_FUNCTION_FORE_COLOR)));
	setBackground(new Color(cfg.getInt(CK.UI_FUNCTION_BACK_COLOR)));
	coordinate = new Color(cfg.getInt(CK.UI_FUNCTION_COORDINATES_COLOR));
	// =======================================================================
	this.addComponentListener(new ComponentResizedListener());
    }

    // ============ getters & setters =============
    public void addModel(CanvasModel model) {
	model.addCanvasModelListener(this);
	models.add(model);
	curves.add(new Path2D.Double());
    }

    public void removeModel(CanvasModel model) {
    }

    public Color getCoordinate() {
	return this.coordinate;
    }

    public void setCoordinate(Color coordinatesColor) {
	this.coordinate = coordinatesColor;
	repaint();
    }

    public YAxisPolicy getYAxisPolicy() {
	return this.yAxisPolicy;
    }

    public void setYAxisPolicy(YAxisPolicy axisPolicy) {
	this.yAxisPolicy = axisPolicy;
	this.repaint();
    }

    public void modelChanged(CanvasModelEvent event) {
	repaint();
    }

    public boolean saveImage(File file, String formatName) throws IOException {
	if (null == img)
	    return false;
	img.flush();
	return ImageIO.write(img, formatName, file);
    }

    protected void paintComponent(Graphics g) {
	if (totalWidth <= 1)
	    return;
	this.getInsets(borderInsets);
	// =======================
	// clear background
	g2.setColor(this.getBackground());
	g2.fillRect(-totalWidth / 2, -totalHeight / 2, img.getWidth(), img.getHeight());
	// draw coordinates
	this.drawCoordinates();
	// ========================
	g2.setColor(this.getForeground());
	for (Path2D.Double path : this.curves)
	    g2.draw(path);
	g2.setColor(Color.lightGray);
	int x = totalWidth > totalHeight ? totalWidth : totalHeight;
	x /= 2;
	g2.drawLine(-x, -x, x, x);
	// ========================
	g.drawImage(img, borderInsets.left, borderInsets.top, this);
    }

    private void computeModel(CanvasModel model, Path2D.Double path) {
	path.reset();
	y.clear();
	double rangeX = maxX - minX;
	int x = canvasWidth / 2;
	if (1 == Integer.lowestOneBit(canvasWidth))// ����
	    x++;
	double minY = Double.MAX_VALUE;
	double maxY = Double.MIN_VALUE;
	for (int i = -x; i < x; i++) {
	    double value = this.function(model, rangeX * i / canvasWidth);
	    if (value < minY)
		minY = value;
	    if (value > maxY)
		maxY = value;
	    y.add(value);
	}
	// transform y
	if (yAxisPolicy == YAxisPolicy.AUTO_SCALE) {
	    double rangeY = maxY - minY;
	    for (int i = 0; i < y.size(); i++)
		y.set(i, y.get(i) * canvasHeight / rangeY);
	} else if (yAxisPolicy == YAxisPolicy.BIND_TO_X) {
	    for (int i = 0; i < y.size(); i++)
		y.set(i, y.get(i) * canvasWidth / rangeX);
	} else if (yAxisPolicy == YAxisPolicy.CUSTOM) {
	    // not implement yet
	}
	path.moveTo(-x, y.get(0));
	// =======================
	for (int i = -x + 1, j = 1; i < x; i++)
	    path.lineTo(i, y.get(j++));
    }

    private double function(CanvasModel model, double value) {
	return model.getValue(UserSymbolTable.getInstance(), value);
    }

    private int cdGap = 25;

    // ============================
    private void drawCoordinates() {
	g2.setColor(coordinate);
	g2.draw(new Rectangle.Double(-canvasWidth / 2D, -canvasHeight / 2D, canvasWidth, canvasHeight));
	// x
	double x1 = -canvasWidth / 2d, y1 = -canvasHeight / 2d;
	double x2 = -x1, y2 = -y1;
	for (double x = 0; x < x2; x += cdGap) {
	    drawLine(g2, x, y1, x, y2);
	    drawLine(g2, -x, y1, -x, y2);
	}
	// y
	for (double y = 0; y < y2; y += cdGap) {
	    drawLine(g2, x1, y, x2, y);
	    drawLine(g2, x1, -y, x2, -y);
	}
	// coordinates
	g2.setColor(Color.black);
	drawLine(g2, x1, 0, x2, 0);
	drawLine(g2, 0, y1, 0, y2);
    }

    private Line2D tmpLine = new Line2D.Double();

    private void drawLine(Graphics2D g2, double x1, double y1, double x2, double y2) {
	tmpLine.setLine(x1, y1, x2, y2);
	g2.draw(tmpLine);
    }

    class ComponentResizedListener extends ComponentAdapter {
	private int widthConstant = 2;

	@Override
	public void componentResized(ComponentEvent e) {
	    // canvas size changed
	    getInsets(borderInsets);
	    totalWidth = getWidth() - borderInsets.left - borderInsets.right;
	    totalHeight = getHeight() - borderInsets.top - borderInsets.bottom;
	    img = new BufferedImage(totalWidth, totalHeight, BufferedImage.TYPE_4BYTE_ABGR);
	    int th = getFontMetrics(getFont()).getHeight();
	    canvasInsets.set(th, th * widthConstant, th, th * widthConstant);
	    canvasWidth = totalWidth - canvasInsets.left - canvasInsets.right;
	    canvasHeight = totalHeight - canvasInsets.top - canvasInsets.bottom;
	    // ======================================
	    g2 = img.createGraphics();
	    g2.setTransform(new AffineTransform(1, 0, 0, -1, totalWidth / 2.0D, totalHeight / 2.0D));
	    g2.setStroke(stroke);
	    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
	    // re-generate points
	    if (models.size() > 0) {
		for (int i = 0; i < models.size(); i++) {
		    CanvasModel model = models.get(i);
		    Path2D.Double path = curves.get(i);
		    computeModel(model, path);
		}
	    }
	    repaint();
	}
    }
}
